#include "hls_protocol_client.h"

#include "public_module/file_module/file_handle.h"
#include "public_module/hls_protocol/hls_m3u8_message.h"
#include "public_module/http_protocol/http_protocol_msg.h"
#include "public_module/openssl_util/openssl_util.h"
#include "public_module/protocol_center/protocol_parser.h"
#include "public_module/protocol_connection/hls_protocol_operation.h"
#include "public_module/protocol_connection/http_protocol_operation.h"
#include "public_module/protocol_connection/protocol_connection.h"
#include "public_module/protocol_connection/protocol_operation.h"

#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include <cstring>
#include <vector>

HlsProtocolClient::HlsProtocolClient(Loop * loop)
{
    m_loop            = loop;
    m_hls_file_path   = "./";
    m_media_file_path = "./";
}

void HlsProtocolClient::Path(const std::string & hls_file_path, const std::string & media_file_path)
{
    m_hls_file_path   = hls_file_path;
    m_media_file_path = media_file_path;
}

void HlsProtocolClient::SendRequest(ProtocolConnection * connection, HttpRequest & request) const
{
    (void)this;
    auto        protocol_data = connection->ProtocolData();
    request.AddHeaderLine("GET", protocol_data.m_uri, "HTTP/1.1");
    request.AddHeaderSign("User-Agent", "curl/8.4.0-DEV");
    request.AddHeaderSign("Connection", "keep-alive");
    request.AddHeaderSign("Accept", "*/*");
    request.AddHeaderSign("Host", protocol_data.m_host + ":" + std::to_string(protocol_data.m_port));
    std::string content;
    request.String(content);
    connection->SendMessage((const uint8_t *)content.c_str(), content.size());
}

void HlsProtocolClient::SendHeadRequest(ProtocolConnection * connection) const
{
    (void)this;
    auto * operation = (HttpProtocolOperation*)connection->GetProtocolOperation();
    auto        protocol_data = connection->ProtocolData();
    HttpRequest & request = *operation->HttpRequestMsg();
    request.AddHeaderLine("HEAD", protocol_data.m_uri, "HTTP/1.1");
    request.AddHeaderSign("User-Agent", "curl/8.4.0-DEV");
    request.AddHeaderSign("Connection", "keep-alive");
    request.AddHeaderSign("Accept", "*/*");
    request.AddHeaderSign("Host", protocol_data.m_host + ":" + std::to_string(protocol_data.m_port));
    std::string content;
    request.String(content);
    connection->SendMessage((const uint8_t *)content.c_str(), content.size());
}

int HlsProtocolClient::WriteM3U8File(const std::string & filename, const std::string & content) const
{
    (void)this;
    FileHandle file(filename);
    int        result = file.OpenFileReadWrite();
    if (result < 0)
    {
        return -1;
    }
    file.Write((const uint8_t *)content.c_str(), content.size());
    return 0;
}

void HlsProtocolClient::WriteToFile(const std::string & filename, std::vector<uint8_t> & data) const
{
    // maybe '/' charactor is repeat
    std::string file_path = m_media_file_path + filename;
    FileHandle  file(file_path);
    int         result = file.OpenFileReadWrite();
    if (result < 0)
    {
        // open failed;
        printf("write %s failed.\n", file_path.c_str());
    }
    else
    {
        file.Write(data.data(), data.size());
    }
}
void HlsProtocolClient::WriteToFileAppend(const std::string & filename, std::vector<uint8_t> & data) const
{
    std::string file_path = m_media_file_path + filename;
    FileHandle  file(file_path);
    int         result = file.OpenFileAppendWrite();
    if (result < 0)
    {
        // open failed;
        printf("write %s failed.\n", file_path.c_str());
    }
    else
    {
        file.Write(data.data(), data.size());
    }
}

int HlsProtocolClient::InitM3U8Url(const std::string & url)
{
    m_m3u8_url       = url;
    m_hls_operate    = new HlsProtocolOperation();
    m_hls_connection = new ProtocolConnection(m_loop, m_hls_operate);
    m_hls_connection->OnProtocolConnect([this](ProtocolConnection *, int result) { OnConnection(m_hls_connection, result); });
    m_hls_connection->OnProtocolMessage(
        [this](ProtocolConnection *, ProtocolOperation *) { OnMessage(m_hls_connection, m_hls_operate); });
    m_hls_connection->OnProtocolConnErr([this](ProtocolConnection *, int result) { OnConnError(m_hls_connection, result); });
    m_hls_connection->InitServerUrl(url);
    return 0;
}

void HlsProtocolClient::OnConnection(ProtocolConnection * connection, int result) const
{
    if (result < 0)
    {
        printf("connect failed.\n");
    }
    else
    {
        SendRequest(connection, *m_hls_operate->HttpProtocolOperate()->HttpRequestMsg());
    }
}

void HlsProtocolClient::OnMessage(ProtocolConnection * connection, HlsProtocolOperation * operate)
{
    if (m_hls_operate == operate)
    {
        auto        protocol_data = connection->ProtocolData();
        auto        message       = m_hls_operate->M3U8Msg();
        auto        uri_index     = message->UriIndex();
        auto        index         = m_m3u8_url.rfind('/');
        std::string compose_uri   = m_m3u8_url.substr(0, index + 1);
        for (auto & data : uri_index)
        {
            printf("%s%s.\n", compose_uri.c_str(), data.m_index_uri.c_str());
            std::string md5;
            if (OpenSSLUtil::ComputeFileMD5(data.m_index_uri, md5) != -1)
            {
                md5 = "\"" + md5 + "\"";
                m_media_url_vec.push_back({.m_file_md5 = md5,.m_url = compose_uri + data.m_index_uri,});
            }
            else
            {
                m_media_url_vec.push_back({.m_url = compose_uri + data.m_index_uri,});
            }
        }
        std::string content;
        HlsM3U8MessageUtil::BuildM3U8Content(message, m_media_file_path, content);
        int res = WriteM3U8File("./index.m3u8", content);
        if (res < 0)
        {
            printf("write hls m3u8 file failed errno : %d.\n", errno);
            connection->CloseConnection();
            return;
        }
        for (int index = 0; index < 8; index++)
        {
            auto request_media_conn = std::make_shared<RequestMediaData>();
            request_media_conn->m_http_operate    = new HttpProtocolOperation(HttpProtocolOperation::RESPONSE);
            request_media_conn->m_http_connection = new ProtocolConnection(m_loop, request_media_conn->m_http_operate);
            request_media_conn->m_http_connection->OnProtocolConnect([this, request_media_conn](ProtocolConnection * conn, int result) {
                ResourceOnConnect(conn, result, request_media_conn);
            });
            request_media_conn->m_http_connection->OnProtocolMessage([this, request_media_conn](ProtocolConnection * conn, ProtocolOperation * op) {

                ResourceOnMessage(conn, static_cast<HlsProtocolOperation *>(op), request_media_conn);
            });
            request_media_conn->m_http_connection->OnProtocolDisConn([this, request_media_conn](auto conn, int result) {

                ResourceOnConnError(conn, result, request_media_conn);
            });
            request_media_conn->m_http_connection->InitServerUrl(m_m3u8_url);
            m_request_media_vec.push_back(request_media_conn);
        }
        operate->ClearMsg();
        connection->CloseConnection();
    }
}

void HlsProtocolClient::OnConnError(ProtocolConnection * connection, int result)
{
    m_hls_connection->CloseConnection();
    delete m_hls_connection;
    m_hls_connection = nullptr;
}

void HlsProtocolClient::ResourceOnConnect(ProtocolConnection * connection, int result, std::shared_ptr<RequestMediaData> request_media_conn)
{
    if (result >= 0)
    {
        if (!m_media_url_vec.empty())
        {
            auto uri = *m_media_url_vec.begin();
            m_media_url_vec.erase(m_media_url_vec.begin());
            connection->InitServerUrl(uri.m_url);
            request_media_conn->m_uri = uri;
            printf("will download resource uri : %s.\n", uri.m_url.c_str());
            if (uri.m_file_md5.empty())
            {
                SendRequest(connection, *request_media_conn->m_http_operate->HttpRequestMsg());
            }
            else
            {
                SendHeadRequest(connection);
            }
        }
        else
        {
            connection->CloseConnection();
        }
    }
    else
    {
        connection->CloseConnection();
    }
}

void HlsProtocolClient::ResourceOnMessage(ProtocolConnection * connection, HlsProtocolOperation * operate, std::shared_ptr<RequestMediaData> request_media_conn)
{
    auto response = request_media_conn->m_http_operate->HttpResponseMsg();
    printf("http response code : %s.\n", response->GetCode().c_str());
    if (response->GetCode() == "200")
    {
        bool next = false;
        printf("uri : %s media size : %ld.\n", request_media_conn->m_uri.m_url.c_str(), response->Body().size());
        if (request_media_conn->m_uri.m_file_md5.empty())
        {
            std::string url = connection->ProtocolData().m_uri;
            auto        pos = url.rfind('/');
            WriteToFile(url.substr(pos + 1), response->Body());
            next = true;
        }
        else
        {
            std::string etag = response->GetHeaderValue("Etag");
            if (strcasecmp(request_media_conn->m_uri.m_file_md5.c_str(), etag.c_str()) == 0)
            {
                next = true;
                printf("match etag.\n");
            }
            else
            {
                next = false;
                SendRequest(connection, *request_media_conn->m_http_operate->HttpRequestMsg());
            }
        }
        if (next)
        {
            if (!m_media_url_vec.empty())
            {
                auto uri = *m_media_url_vec.begin();
                m_media_url_vec.erase(m_media_url_vec.begin());
                connection->InitServerUrl(uri.m_url);
                SendRequest(connection, *request_media_conn->m_http_operate->HttpRequestMsg());
                request_media_conn->m_uri = uri;
            }
            else
            {
                request_media_conn->m_uri.m_url.clear();
                connection->CloseConnection();
            }
        }
    }
    else
    {
        m_media_url_vec.push_back(request_media_conn->m_uri);
        request_media_conn->m_uri.m_url.clear();
        connection->CloseConnection();
    }
    request_media_conn->m_http_operate->ClearMsg();
}

void HlsProtocolClient::ResourceOnConnError(ProtocolConnection * connection, int, std::shared_ptr<RequestMediaData> request_media_conn)
{
    if (!request_media_conn->m_uri.m_url.empty())
    {
        m_media_url_vec.push_back(request_media_conn->m_uri);
    }
    printf("one connection closed.\n");
    connection->CloseConnection();
}